import gugga.collections.LinkedList;
import gugga.collections.IIterator;

import test_framework.test.unit.TestCase;

import gugga.utils.DebugUtils;

/**
 * This is an utility class that can help you test functionallity 
 * asynchronously, especially for test cases, which are more complex - e.g. we 
 * may want to execute some functionallity, and after a certain delay to test
 * the results, and after this test to execute another portion of 
 * functionallity and after another delay to test the results again. And we 
 * need to package all portions of this test in a single test case.
 * <p>
 * Basic usage:
 * <code>
 * 	<pre>
 * 	class SomeTest extends TestCase
 * 	{
 * 		public function SomeTest()
 * 		{
 * 			super;
 * 		}
 * 		
 * 		public function testComplexFunctionallity()
 * 		{
 * 			var delayInterval:Number = 1000; // in miliseconds
 * 			var testSequence:PauseResumeMethodsSequence 
 * 				= new PauseResumeMethodsSequence(
 * 						this, 
 * 						delayInterval,
 * 						[initializeTest, 
 * 						assertsAfterInitialization, 
 * 						actionsAfterInitialization, 
 * 						assertsAfterActions]);
 * 			
 * 			testSequence.start();
 * 		}
 * 		
 * 		private function initializeTest()
 * 		{
 * 		}
 * 		
 * 		private function assertsAfterInitialization()
 * 		{
 * 		}
 * 		
 * 		private function actionsAfterInitialization()
 * 		{
 * 		}
 * 		
 * 		private function assertsAfterActions()
 * 		{
 * 		}
 * 	}
 * 	</pre>
 * </code>
 * <p>
 * This class can help you decouple complex test cases.
 * 
 * @author vladimir
 * @see TaskExecutionEvaluator
 */
class guggaLibTests.support.PauseResumeMethodsSequence
		extends TestCase 
{
	private var parentTestCase:TestCase;
	private var delayIntervalID:Number;
	private var delayInterval:Number;
	private var methodsList:LinkedList;
	private var methodsIterator:IIterator;
	
	public function PauseResumeMethodsSequence(
			aParentTestCase:TestCase,
			aDelayInterval:Number, 
			aMethodsArray:Array)
	{
		super();
		
		this.parentTestCase = aParentTestCase;
		this.delayInterval = aDelayInterval;
		this.delayIntervalID = null;
		this.initializeMethodsList(aMethodsArray);
		this.initializeMethodsIterator();
	}
	
	public function start()
	{
		this.applyCurrentMethod();
	}
	
	private function initializeMethodsList(aMethodsArray:Array)
	{
		this.methodsList = new LinkedList();
		
		for(var i:Number = 0; i < aMethodsArray.length; i++) 
		{
			this.methodsList.insertTail(aMethodsArray[i]);
		}
	}
	
	private function initializeMethodsIterator()
	{
		this.methodsIterator = this.methodsList.getIterator();
		
		// position the iterator on the first element in the methods list:
		this.methodsIterator.iterate();
	}
	
	private function clearIntervalAndResume()
	{
		if(this.delayIntervalID)
		{
			clearInterval(this.delayIntervalID);
			this.parentTestCase.resume();
		}
	}
	
	private function setNextDelayInterval()
	{
		this.delayIntervalID = setInterval(
				createMethodForDelayedExecution(this, applyCurrentMethod), 
				this.delayInterval);
	}
	
	private function applyCurrentMethod()
	{	
		var currentMethod:Function = Function(this.methodsIterator.current());
		
		this.clearIntervalAndResume();
		this.setNextDelayInterval();
		
		currentMethod.apply(this.parentTestCase, []);
		
		this.getNextMethodAndPause();
	}
	
	private function getNextMethodAndPause()
	{
		if(this.methodsIterator.iterate())
		{
			this.parentTestCase.pause();
		}
	}
	
	private function createMethodForDelayedExecution(
			aObject:Object, 
			aMethod:Function)
	{
		var resultMethod = function()
		{
			var target = arguments.callee.target;
			var func = arguments.callee.func;

			return func.apply(target, arguments);
		};

		resultMethod.target = aObject;
		resultMethod.func = aMethod;

		return resultMethod;
	}
}